Continuing Education - DIME Analytics
Development Impact Evaluation (DECDI)
Tuesday, the 20th of May, 2025
Welcome to this interactive Shiny session! In the next 90 minutes, we will:
UI + Serverui.R, server.R, global.R)This session is live at: 🔗 [https://ce-wb-shiny.netlify.app/]
You can find the final solutions (both single-file and multiple-file apps) in our GitHub repository: 📦 [pending]
Shiny is a web application framework for R that allows you to turn analyses into interactive web apps — all in R.
Why use it?
A Shiny app has two core components:
Apps are served to users via a host and port. The R session running the server reacts to user actions, computes results, and sends them back.
Client: The web browser where the user interacts with the app
Host:Port: Shiny app is served at an IP (host) and port
Server: Runs R session to monitor inputs and respond with outputs
Step-by-step instructions:
5. Choose Single File option when prompted:
6. Name your folder and click OK
7. Click Run App in the top-right corner
8. 🎉 You’re running your first Shiny app!
Go to the app you just created and let’s explore each element
ui — User Interfaceui <- fluidPage( #<<
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30)
),
mainPanel(
plotOutput("distPlot")
)
)
)Layout elements
fluidPage() is the container for the app interface, the layout in which your content is. This is the most common, but there are other types of layouts.Go to the app you just created and let’s explore each element
ui — User Interfaceui <- fluidPage(
titlePanel("Old Faithful Geyser Data"),
sidebarLayout( #<<
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30)
),
mainPanel(
plotOutput("distPlot")
)
)
)Layout elements
sidebarLayout() splits the layout into sidebar (sidebarPanel()) and main area (mainPanel()). This is also optional.Go to the app you just created and let’s explore each element
ui — User Interfaceui <- fluidPage(
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
sidebarPanel( #<<
sliderInput("bins", #<<
"Number of bins:", #<<
min = 1, #<<
max = 50, #<<
value = 30) #<<
),
mainPanel(
plotOutput("distPlot")
)
)
)Page content
sidebarPanel() contains one input field (sliderInput()) called “bins”. As you can infer from the name, this is a slider that lets a user choose a number. In this case, the number of histogram bins.Go to the app you just created and let’s explore each element
ui — User Interfaceui <- fluidPage(
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30)
),
mainPanel( #<<
plotOutput("distPlot") #<<
)
)
)Page content
mainPanel() contains a histogram (plotOutput()), which will be defined in the server function. The name of this histogram is “distPlot”server — Server Logicserver <- function(input, output) {
output$distPlot <- renderPlot({
x <- faithful[, 2]
bins <- seq(min(x), max(x), length.out = input$bins + 1)
hist(x, breaks = bins, col = 'darkgray', border = 'white',
xlab = 'Waiting time to next eruption (in mins)',
main = 'Histogram of waiting times')
})
}The server() function takes two arguments:
input: a reactive list of input values from the UIoutput: a reactive list where you assign render functionsIn the default Shiny app, the server creates one object: the histogram distPlot.
output$distPlot → creates the plot output using renderPlot()input$bins → pulls the number from the slider input in the UIRemember these connections?
output$distPlot <- renderPlot() ↔︎ plotOutput("distPlot") in the UI
input$bins ↔︎ sliderInput("bins", ...) in the UI
This is what makes Shiny reactive — inputs update outputs automatically!
Shiny uses reactive programming — a way to make your app automatically update outputs when inputs change.
outputId: Matches the ID of the output element in the UI (e.g., plotOutput("distPlot"))renderFunction: A special function like renderPlot(), renderTable(), etc.input$... changes!In our example:
sliderInput("bins", ...) → sets input$binsrenderPlot() → uses input$bins to create a histograminput and output behave like reactive lists — not regular R lists, but special objects in Shiny.input$bins = 5.input list — and any render function using it will re-execute.output$distPlot <- renderPlot({ ... input$bins ... }) updates instantly.Together, input, output, and render*() functions form the reactive backbone of your app.
Shiny apps are built by connecting inputs (from the UI) to outputs (rendered in the server).
| Part | Role | Examples |
|---|---|---|
ui |
Define layout and inputs/outputs | sliderInput(), plotOutput() |
server |
Logic to render outputs based on inputs | renderPlot(), renderText() |
🔁 Reactivity connects them:
input$... pulls values from UI controlsoutput$... <- render...() generates dynamic contentShiny includes many built-in widgets to capture user input:
| Widget | Purpose | Example Use |
|---|---|---|
numericInput() |
Enter a number | Age, price |
sliderInput() |
Select from a range | Histogram bins |
selectInput() |
Choose from a list | Country selector |
radioButtons() |
Choose one option | Plot type |
textInput() |
Enter text | Comments, filters |
fileInput() |
Upload a file | CSV, Excel |
actionButton() |
Trigger an action manually | Run, Submit |
📚 See the full gallery: ➡️ Shiny Widgets Gallery
#ca8dfd (a shade of purple)After your modifications the app should look like this:
Before you close the app, check the R console. You’ll see something like:
🔍 What it means: - 127.0.0.1 refers to your local machine (“localhost”) - 3827 is a random port number - You can open the app in any browser using this address
⛔ While the app is running: - The R console is blocked (no new commands allowed) - A stop sign appears in the RStudio toolbar
🛑 To stop the app: - Click the stop sign icon - Press Esc (or Ctrl + C in terminal) - Close the Shiny app window
As your app grows, managing everything in a single file becomes difficult. That’s why it’s a good idea to switch to a multi-file structure — this is the recommended approach.
Let’s walk through how to set it up!
In RStudio, go to
File > New File > Shiny Web App…
This time, choose “Multiple File” when prompted:
3. Name your project folder and click OK. This will automatically create two files.
4. Lastly let’s create an extra file global.R. (Optional but recommended) This file is useful for loading packages and defining global objects or functions used by both ui.R and server.R.
5. Click the Run App button in the top-right corner of RStudio.
Here’s a clearer and more engaging version of your slide:
You’ve set up a multiple-file Shiny app—great start! Now let’s customize it together.
We’ll go through a series of hands-on exercises using the faithful dataset to:
After each exercise, we’ll do a live walkthrough to see how the changes integrate into the app.
📝 Note: While we’re using the files created by RStudio as a starting point, you’re not limited to that setup. You can always:
.R scripts as ui.R and server.Rapp.R file if you prefer that styleLet’s improve the layout and presentation of your app!
In ui.R, replace the titlePanel() with:
Let’s make the histogram more interactive!
Add a selectInput() to the sidebarPanel() so users can choose a color for the histogram
Then use input$color inside renderPlot() to apply the color.
See that if I don’t add the input$color in the server inside the hist() function, the color will not change.
Ok! now let’s make this more challenging! Let’s give the user control over the type of plot they see!
Add a radioButtons() input to let the user choose between:
"Histogram" of waiting times"Scatterplot" of eruption duration vs. waiting timeModify renderPlot() in server.R to change behavior based on selection
In server.R, check the value of input$plot_type to decide which plot to draw.
ui.RFull server logig:
server.Rfunction(input, output, session) {
output$distPlot <- renderPlot({
# generate bins based on input$bins from ui.R
x <- faithful[, 2]
if (input$plot_type == "Histogram") {
bins <- seq(min(x), max(x), length.out = input$bins + 1)
# draw the histogram with the specified number of bins
hist(x, breaks = bins, col = input$color, border = 'white',
xlab = 'Waiting time to next eruption (in mins)',
main = 'Histogram of waiting times')
} else if (input$plot_type == "Density")
{
plot(
density(x),
col = input$color,
lwd = 2,
xlab = "Waiting time to next eruption (mins)",
main = "Density plot of waiting times"
)
}
})
}It’s always good practice to explain what your app does. For this, we can create an intro tab — like a README page — that gives your users helpful context.
Add a tabsetPanel() inside the mainPanel().
Create two tabs:
In the Intro tab, write a short description of what the app does (in plain text or with HTML).
Here’s how your ui.R could look after adding the tabs:
navbarPage("Faithful Geyser Data - Customized",
tabPanel("Intro",
fluidPage(
h3("Welcome to the Faithful Geyser Data App!")
)
),
tabPanel("Plots",
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30),
selectInput("color", "Choose a color:", choices = c("turquoise", "plum", "orchid")),
radioButtons("plot_type", "Choose a plot type:",
choices = c("Histogram", "Density"))
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
)
)Want your app to look more polished? Shiny supports easy theming with the {bslib} package.
👉 Your task: Add a custom theme to your app!
Load the bslib package in your global.R file:
If you don’t have it installed, run:
Wrap your navbarPage() in a thematic Bootstrap theme:
Save and re-run your app!
"flatly" (clean + modern)"darkly" (dark mode)"minty" (playful + bright)"journal" (serif style)Let’s allow users to download the dataset they are exploring!
downloadButton() to the UI so users can download the data.server.R, define a downloadHandler() to write the faithful dataset as a CSV file.downloadButton() in the ui and downloadHandler() in the server.ui.Rserver.Routput$download_data <- downloadHandler(
filename = function() { "faithful_data.csv" },
content = function(file) {
write.csv(faithful, file, row.names = FALSE)
}
)
You can share your Shiny app with others by deploying it to a server. Here are some options:
Want to go further with Shiny? Here are some helpful resources:
🧪 Shiny Tutorial (Official Getting Started Guide)
https://shiny.posit.co/tutorial
📘 Mastering Shiny by Hadley Wickham (Free online book)
https://mastering-shiny.org
💡 Shiny Widgets Gallery
https://shiny.posit.co/r/gallery/widgets/widget-gallery/
🧩 Awesome Shiny Extensions (Community plugins)
https://github.com/nanxstats/awesome-shiny-extensions
🌐 Shiny Community (Forums, discussions) https://community.rstudio.com/c/shiny
DIME theme for Quarto Presentations. Code available on GitHub.